
package de.uni.wuppertal.server.ui;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Vector;

import javax.swing.*;

import com.sun.pdfview.PDFFile;
import com.sun.pdfview.PDFPage;
import com.sun.pdfview.PagePanel;

import de.uni.wuppertal.server.util.Resource;

/**
 * @author Martin Schröder
 * @author Patrik Nordsiek
 */
public class HelpDialog extends JFrame
{
	/**
	 * Serialisierung.
	 */
	private static final long serialVersionUID = 1;
	
	/**
	 * Button zum öffnen der PDF-Datei im Standardprogramm des Systems.
	 */
	private JButton buttonOpen;
	
	/**
	 * Button zum Schließen der Hilfe.
	 */
	private JButton buttonClose;
	
	/**
	 * Angezeigte Seiten der PDF-Datei.
	 */
	private Vector<PagePanel> pdfPagePanels;
	
	/**
	 * Temporäre Kopie der Hilfe, ist notwendig um auch aus JAR-Archiven
	 * das Standardprogramm des Systems aufzurufen.
	 */
	private File tmpFile;
	
	/**
	 * PDF-Daten im Hauptspeicher.
	 */
	private PDFFile pdfFile;
	
	/**
	 * Panel für die Buttons.
	 */
	private JPanel buttonPanel;
	
	/**
	 * Hauptpanel des Fensters.
	 */
	private JPanel mainPanel;
	
	/**
	 * Panel für das ScrollPane.
	 */
	private JPanel scrollAreaPanel;
	
	/**
	 * Scroller für die Seiten des PDF.
	 */
	private JScrollPane pageScrollPane;
	
	/**
	 * Enthält die einzelnen Seiten des PDF.
	 */
	private JPanel pageContainerPanel;
	
	/**
	 * Hält den Rahmen um den Scrollbereich.
	 */
	private JPanel titledBorderPanel;
	
	/**
	 * Erzeugt die Hilfe.
	 * 
	 * @param parent Hauptprogramm
	 */
	public HelpDialog(JFrame parent)
	{
		super("Hilfe - Serververwaltung");
		
		setDefaultCloseOperation(HIDE_ON_CLOSE);
		
		setIconImage(Resource.getIcon("icon/server.png").getImage());
		
		setLocation(10, 5);
		
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		
		Dimension max = new Dimension();
		max.setSize(screenSize.getWidth() - 100, screenSize.getHeight() - 100);
		
		loadPDF();
		
		PDFPage firstPage = pdfFile.getPage(0, true);
		
		Dimension pageDimension = new Dimension((int)firstPage.getWidth(), (int)firstPage.getHeight());
		
		double scale = 172.0 / Toolkit.getDefaultToolkit().getScreenResolution();
		pageDimension = firstPage.getUnstretchedSize((int)(firstPage.getHeight() * scale), (int)(firstPage.getWidth() * scale), null);
		
		createMainPanel(max);
		createScrollArea();
		createPagePanels(pageDimension);
		createButtons();
		
		pack();
		
		Dimension target = new Dimension();
		target.setSize(Math.min(max.getWidth(), mainPanel.getWidth()) + 10, Math.min(max.getHeight(), mainPanel.getHeight()));
		
		setPreferredSize(target);
		
		validate();
		pack();
		
		pdfPagePanels.get(0).showPage(firstPage);
		
		setResizable(true);
		
		enqueueRemainingPages();
	}
	
	/**
	 * Erzeugt den Scrollbereich mit allen Umrandungen und Layouts.
	 */
	private void createScrollArea()
	{
		scrollAreaPanel = new JPanel(new GridBagLayout());
		pageContainerPanel = new JPanel(new GridLayout(pdfFile.getNumPages(), 3, 0, 5));
		
		GridBagConstraints constraints = new GridBagConstraints();
		
		constraints.anchor = GridBagConstraints.CENTER;
		
		scrollAreaPanel.add(pageContainerPanel, constraints);
		
		pageContainerPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		
		pageScrollPane = new JScrollPane(scrollAreaPanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
		
		pageScrollPane.getHorizontalScrollBar().setUnitIncrement(30);
		pageScrollPane.getVerticalScrollBar().setUnitIncrement(30);
		
		titledBorderPanel = new JPanel(new BorderLayout(5, 5));
		
		titledBorderPanel.setBorder(BorderFactory.createTitledBorder("Handbuch für Server Version 1.0"));
		
		titledBorderPanel.add(pageScrollPane);
		
		mainPanel.add(titledBorderPanel, BorderLayout.CENTER);
	}
	
	/**
	 * Erzeugt das Hauptpanel.
	 * 
	 * @param max Maximale Größe für den Bildschirm
	 */
	private void createMainPanel(Dimension max)
	{
		mainPanel = new JPanel(new BorderLayout(5, 5));
		
		mainPanel.setMaximumSize(max);
		mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		
		setContentPane(mainPanel);
	}
	
	/**
	 * Erzeugt die Buttons und ihre Aktionen.
	 */
	private void createButtons()
	{
		buttonPanel = new JPanel(new GridLayout(1, 4, 5, 0));
		
		mainPanel.add(buttonPanel, BorderLayout.SOUTH);
		
		buttonPanel.add(new JPanel());
		buttonPanel.add(new JPanel());
		
		buttonOpen = new JButton("Als PDF-Datei öffnen");
		
		buttonOpen.setMnemonic('P');
		buttonOpen.setToolTipText("Die Hilfe mit dem Standardprogramm zum Anzeigen von PDF-Dateien öffnen");
		buttonOpen.setEnabled(Desktop.getDesktop().isSupported(Desktop.Action.OPEN));
		
		buttonPanel.add(buttonOpen);
		
		buttonOpen.addActionListener(new ActionListener()
		{
			/**
			 * PDF-Datei im Standardprogramm öffnen.
			 */
			@Override
			public void actionPerformed(ActionEvent event)
			{
				try
				{
					Desktop desktop = Desktop.getDesktop();
					
					desktop.open(tmpFile);
				}
				catch(Exception e)
				{
					throw new RuntimeException("PDF-Datei kann nicht geöffnet werden.", e);
				}
			}
		});
		
		buttonClose = new JButton("Schließen");
		
		buttonClose.setMnemonic('S');
		buttonClose.setToolTipText("Fenster schließen");
		
		buttonPanel.add(buttonClose);
		
		buttonClose.addActionListener(new ActionListener()
		{
			/**
			 * Das Fenster wird unsichtbar.
			 */
			@Override
			public void actionPerformed(ActionEvent event)
			{
				dispose();
			}
		});
		
		getRootPane().setDefaultButton(buttonClose);
	}
	
	/**
	 * Erzeugen der Panels für die Seiten des PDF.
	 * 
	 * @param pageDimension Größe einer PDF-Seite
	 */
	private void createPagePanels(Dimension pageDimension)
	{
		pdfPagePanels = new Vector<PagePanel>();
		
		for(int i = 0; i < pdfFile.getNumPages(); i++)
		{
			PagePanel current = new PagePanel();
			
			pdfPagePanels.add(current);
			
			JPanel container = new JPanel(new GridLayout(1, 1));
			
			container.setBorder(BorderFactory.createEtchedBorder());
			
			current.setPreferredSize(pageDimension);
			current.setOpaque(true);
			current.useZoomTool(false);
			current.setBorder(BorderFactory.createLineBorder(Color.BLUE, 2));
			
			container.add(current);
			
			pageContainerPanel.add(container);
		}
	}
	
	/**
	 * Lädt die Hilfe aus der PDF-Datei.
	 */
	private synchronized void loadPDF()
	{
		try
		{
			tmpFile = File.createTempFile("tit", ".pdf");
			tmpFile.deleteOnExit();
			
			InputStream in = Resource.getInputStream("help.pdf");
			FileOutputStream out = new FileOutputStream(tmpFile);
			
			byte[] bytes = new byte[4096];
			int count = 0;
			
			while((count = in.read(bytes)) != -1)
			{
				out.write(bytes, 0, count);
			}
			
			in.close();
			out.close();
			
			RandomAccessFile randomAccessFile = new RandomAccessFile(tmpFile, "r");
			FileChannel channel = randomAccessFile.getChannel();
			ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
			
			pdfFile = new PDFFile(buffer);
		}
		catch(Exception e)
		{
			throw new RuntimeException("Fehler Laden der Hilfe", e);
		}
	}
	
	/**
	 * Das Laden der verbleibenden Seiten in einem Thread erledigen.
	 */
	private void enqueueRemainingPages()
	{
		EventQueue.invokeLater(new PageLoader(2, 1));
	}
	
	/**
	 * Thread zum Laden von PDF-Seiten.
	 * 
	 * @author Martin Schröder
	 */
	private class PageLoader extends Thread
	{
		/**
		 * Startseite.
		 */
		private int pageNumber;
		
		/**
		 * Sprunginterval.
		 */
		private int pageOffset;
		
		/**
		 * Erzeugt einen neuen Thread zum Laden der Seiten.
		 * 
		 * @param pageNumber Startseite
		 * @param pageOffset Sprunginterval
		 */
		public PageLoader(int pageNumber, int pageOffset)
		{
			super();
			
			this.pageNumber = pageNumber;
			this.pageOffset = pageOffset;
			
			setDaemon(true);
		}
		
		/**
		 * Lädt die Seiten aus dem PDF und zeichnet sie in die Page Panels.
		 */
		@Override
		public void run()
		{
			int i = pageNumber;
			
			try
			{
				for(; i <= pdfFile.getNumPages(); i += pageOffset)
				{
					PDFPage currentPage = pdfFile.getPage(i);
					
					pdfPagePanels.get(i - 1).showPage(currentPage);
				}
			}
			catch(Exception e)
			{
				throw new RuntimeException("Fehler beim Laden von Seite " + i, e);
			}
		}
	}
}
